/* @flow */

import { warn } from "./warn";

const encodeReserveRE = /[!'()*]/g;
const encodeReserveReplacer = (c) => "%" + c.charCodeAt(0).toString(16);
const commaRE = /%2C/g;

// fixed encodeURIComponent which is more conformant to RFC3986:
// - escapes [!'()*]
// - preserve commas
const encode = (str) =>
  encodeURIComponent(str)
    .replace(encodeReserveRE, encodeReserveReplacer)
    .replace(commaRE, ",");

const decode = decodeURIComponent;

export function resolveQuery(
  query: ?string,
  extraQuery: Dictionary<string> = {},
  _parseQuery: ?Function
): Dictionary<string> {
  const parse = _parseQuery || parseQuery;
  let parsedQuery;
  try {
    parsedQuery = parse(query || "");
  } catch (e) {
    process.env.NODE_ENV !== "production" && warn(false, e.message);
    parsedQuery = {};
  }
  for (const key in extraQuery) {
    parsedQuery[key] = extraQuery[key];
  }
  return parsedQuery;
}

function parseQuery(query: string): Dictionary<string> {
  const res = {};

  query = query.trim().replace(/^(\?|#|&)/, "");

  if (!query) {
    return res;
  }

  query.split("&").forEach((param) => {
    const parts = param.replace(/\+/g, " ").split("=");
    const key = decode(parts.shift());
    const val = parts.length > 0 ? decode(parts.join("=")) : null;

    if (res[key] === undefined) {
      res[key] = val;
    } else if (Array.isArray(res[key])) {
      res[key].push(val);
    } else {
      res[key] = [res[key], val];
    }
  });

  return res;
}

export function stringifyQuery(obj: Dictionary<string>): string {
  const res = obj
    ? Object.keys(obj)
        .map((key) => {
          const val = obj[key];

          if (val === undefined) {
            return "";
          }

          if (val === null) {
            return encode(key);
          }

          if (Array.isArray(val)) {
            const result = [];
            val.forEach((val2) => {
              if (val2 === undefined) {
                return;
              }
              if (val2 === null) {
                result.push(encode(key));
              } else {
                result.push(encode(key) + "=" + encode(val2));
              }
            });
            return result.join("&");
          }

          return encode(key) + "=" + encode(val);
        })
        .filter((x) => x.length > 0)
        .join("&")
    : null;
  return res ? `?${res}` : "";
}
